local jass = require 'jass.common'
local japi = require 'jass.japi'
local dbg = require 'jass.debug'
local runtime = require 'jass.runtime'

local handle_manager = handle_manager_class:create()

local buff = {
    __tostring = function(self) 
		return ('buff[%s]'):format(self.name) 
    end,
    __call = function(self, data) 
		for k,v in pairs(data) do 
			self[k] = v 
		end
		return self
    end,
} 
setmetatable(buff,buff)

buff.dbgcount = 0

local mt = {}
buff.__index = mt 

--结构
mt.type = 'buff'

--buff所有者
mt.owner = nil

--buff来源
mt.source = nil 

--标题
mt.tip = nil

--描述
mt.ubertip = nil

--附加模型
mt.model = nil 

--附加节点
mt.socket = 'overhead' 

--死亡保持
mt.keep = false 

--叠加方式	0 不共存 | 1 共存 
mt.cover_type = 0

--共存上限	只有cover_type = 1 时才有效
mt.cover_max = 1

--控制等级 
mt.control = false 

--位移buff
mt.displace = false 

--负面buff
mt.debuff = false 

--可抵消的
mt.offset = false

--物理buff
mt.physics = false 

--引导buff	1可被控制打断 2免疫打断
mt.channel = 0

--虚拟buff (notify对虚拟buff无效)
mt.virtual = false

--光环buff
mt.aura = false 

--召唤buff
mt.summon = false

--buff自定义类别
mt.class_name = ''

--心跳次数
mt.pulse_count = 0

--禁用计数
mt.enable_count = 0

--buff获得时的回调
mt.on_add = nil

--buff失去时的回调
mt.on_remove = nil

--buff正常到期时的回调
mt.on_finish = nil

--buff在每个周期时的回调
mt.on_pulse = nil

--覆盖同名buff
mt.on_cover = nil

--持续时间
mt.time = -1

--冷却时间
mt.cool = 0.0

--[获取ID]
function mt:get_id()
    return self.name 
end 

--- 执行事件
function mt:notify(name,...)
	-- 虚拟buff则跳过
    if self.virtual then
        return false
    end
    local func = self[name]
    if func then 
        return select(2, xpcall(func, runtime.error_handle, self, ...))
    end 
end 

-- 添加buff
function mt:add()
	if self.added or self.removed then 
		return 
	end 

	self.added = true 
	self.add_time = ac.clock() / 1000

	local unit = self:get_owner()

	-- 单位死亡而且buff不是死亡保持型的buff添加失败
	if not unit:is_alive() and not self:is_keep() then 
		return  
	end

	-- 返回false屏蔽添加
	if unit:dispatch('单位-即将获得状态', unit, self) == false then
		return 
	end

	local name = self.name
	local replace

	if self.cover_type == 0 then 
		-- buff类的不允许共存查找单位身上有没有同名buff
		local original = unit:find_buff(name)
		if original then 
			-- 覆盖成功则删除前任buff
			if original:notify('on_cover',self) then 
				original:remove()
			else
				-- 否则就添加失败
				return original
			end 
		end 
	elseif self.cover_type == 1 then 
		--可以共存的Buff,查表
		if not unit.buff_list then 
			unit.buff_list = {}
		end 
		-- 获取单位同名buff表
		if not unit.buff_list[name] then 
			unit.buff_list[name] = {}
		end 
		local list = unit.buff_list[name]
		for i = 1, #list + 1 do
			local original = list[i]
			if not original then 
				--没有其他buff了,就放这吧
				table.insert(list, i, self)
				--如果自己不在有效区内,则禁用
				if self.cover_max ~= 0 and i > self.cover_max then
					self:disable()
				end
				break
			end 
			replace = original
			if original:notify('on_cover', self, i) then 
				table.insert(list, i, self)
				--如果刚好把原来的buff挤出有效区,则禁用他
				if self.cover_max == i then
					original:disable()
				end
				--如果自己不在有效区内,则禁用
				if self.cover_max ~= 0 and i > self.cover_max then
					self:disable()
				end
				break
			end 
		end 
	end 

	
	buff.dbgcount = buff.dbgcount + 1

	self.owner.buffs[self] = true 
	
	if self.pulse then
		local last_pulse 
		local function pulse()
			last_pulse = self.pulse
			self.cycle_timer = self.owner:loop(self.pulse * 1000, function(t)
				if self.owner and not self.removed then
					if self:is_enable() then 
						self.pulse_count = self.pulse_count + 1
						self:notify('on_pulse',self.pulse_count)
					end
					if self.pulse ~= last_pulse then
						t:remove()
						if not self.removed then
							pulse()
						end
					end
				else
					t:remove()
				end
			end)
		end
		pulse()
	end 

    if self.time >= 0 then
		self:set_remaining(self.time)
	end

	self.removed = nil
	
	if self.cover_type == 1 and not replace then 
		unit:set_data(("buff-public-{%s}"):format(self.name), { })
	end 
	
	if self:is_enable() then 
		if not self.has_on_add then
			self.has_on_add = true
			self:notify('on_add')
			if not self.removed then 
				if (self.model and self.socket) then 
					self:add_effect(self.model, self.socket)
				end 
				self:add_icon()
			end 
		end 

		if self.removed then
			return
		end
	end 

	self:refresh()

	unit:notify('单位-获得状态', unit, self)

	-- 暂停buff移除了

	return self
end 


--- 移除buff
function mt:remove()
	if self.removed then
		return
	end 

	local unit = self.owner 
	local name = self.name 

	self.removed = true

	if self.delay_timer then
		self.delay_timer:remove()
		self.delay_timer = nil 
	end
	if self.cycle_timer then
		self.cycle_timer:remove()
		self.cycle_timer = nil 
	end
    self:gc_clear()
	unit:notify('单位-失去状态', unit, self)


	unit.buffs[self] = nil

	local replace 	-- 如果有前任则让它回来即位 实现无缝衔接
	local meta = true

	if self.cover_type == 1 then 
		if unit.buff_list and unit.buff_list[name] then 
			local list = unit.buff_list[name]
			for i=1,#list do 
				if self == list[i] then 
					table.remove(list,i)
					--进入有效区的buff生效
					if self.cover_max >= i then
						replace = list[self.cover_max]
					end
					break
				end 
			end 
			meta = (#list == 0)
		end 
	end 

	-- 不是被禁用的buff会触发on_remove, 因为禁用的buff本身就已经触发过 on_remove
	if self:is_enable() and self.added and self.has_on_add then 
		self.added = nil 
		self.has_on_add = nil 
		self:remove_effects()
		self:remove_icon()
		self:notify('on_remove')
	end

	if replace then
		replace:enable()
	end 

	if self.cover_type == 1 and meta then 
		unit:set_data(("buff-public-{%s}"):format(self.name), nil)
	end
	
	buff.dbgcount = buff.dbgcount - 1

	self.is_finish = false
end

-- 暂停
mt.pause_count = 0
function mt:pause(flag)
	if self.force then
		return
	end
	if flag == nil then
		flag = true
	end
	if flag then
		self.pause_count = self.pause_count + 1
		if self.pause_count == 1 then
			if self._timer then
				self._timer:pause()
			end
			if self.cycle_timer then
				self.cycle_timer:pause()
			end
		end
	else
		self.pause_count = self.pause_count - 1
		if self.pause_count == 0 then
			if self._timer then
				self._timer:resume()
			end
			if self.cycle_timer then
				self.cycle_timer:resume()
			end
		end
	end
end

-- 获得当前循环剩余时间
function mt:get_pulse()
	return self.cycle_timer:get_remaining()/1000
end

-- 设置循环周期
--	周期
function mt:set_pulse(pulse)
	self.pulse = pulse
end


function mt:gc(target)
	--目前只支持两种自动回收
	--1为function，即属性
	--2为带有remove方法的对象，如特效，计时器，触发
    local skill = self.parent_skill or self
	if not skill._gc_target then
		skill._gc_target = {}
	end
	table.insert(skill._gc_target,target)
end

function mt:gc_clear()
    local skill = self.parent_skill or self
	if skill._gc_target then
		for k,v in ipairs(skill._gc_target) do
			--function针对属性，调用后还原
			if type(v) == 'function' then
				v()
			else
				--remove针对计时器、触发、特效等，调用后删除
				v:remove()
			end
		end
		skill._gc_target = nil
	end
end


--判定buff自定义类别
function mt:is_class(name)
	return self.class_name:find(name)
end

--是否是控制状态
function mt:is_control()
	return self.control
end

--是否是位移效果
function mt:is_displace()
	return self.displace
end 

--能打断引导效果
function mt:is_disrupt()
	if self.disrupt == nil then 
		return (self:is_control()) or (self:is_displace()) and self:is_debuff()
	end 
	return (self.disrupt) 
end 

--是否是虚拟状态
function mt:is_virtual()
	return self.virtual
end

--是否是负面buff
function mt:is_debuff()
    return self.debuff 
end

--是否是物理buff
function mt:is_physics()
	return self.physics
end 

--是一个引导buff
function mt:is_channel()
	return self.channel > 0
end 

--是个光环buff
function mt:is_aura()
	return self.aura 
end

--死亡保持
function mt:is_keep()
	return self.keep 
end

--是否启用
function mt:is_enable()
	return self.enable_count == 0
end

-- 启用buff
function mt:enable()
	if not self.owner:is_alive() and not self:is_keep() then
		return
	end
	self.enable_count = self.enable_count - 1
	if not self.removed and self.added and self.enable_count == 0 and not self.has_on_add then
		self.has_on_add = true
		self:notify('on_add')
		if not self.removed then 
			if (self.model and self.socket) then 
				self:add_effect(self.model, self.socket)
			end 
			self:add_icon()
		end 
	end
end

-- 禁用buff
function mt:disable()
	self.enable_count = self.enable_count + 1
	if not self.removed and self.added and self.enable_count == 1 and self.has_on_add then
		self.has_on_add = nil
        self:gc_clear()
		self:remove_effects()
		self:remove_icon()
		self:notify('on_remove')
	end
end

-- 获取所有者
function mt:get_owner()
	return self.owner 
end 

--[获取冷却时间]
function mt:get_cool()
    return self.cool 
end

--[设置冷却时间]
function mt:set_cool(cool)
    self.cool = cool 
end 

--[处于冷却]
function mt:is_cooling()
    return self:get_cooling() > 0
end

--[获取剩余冷却]
function mt:get_cooling()
    if not self._cooling then 
        return 0 
    end
    return math.max(self._cooling - ac.clock() / 1000 ,0)
end

--[设置冷却]
function mt:set_cooling(cool)
    self._cooling = ac.clock()/1000 + (cool or self:get_cool())
end

--[使进入冷却]
function mt:cooling()
    self:set_cooling(self:get_cool())
end 

---设置Buff时间
---@时间
function mt:set_remaining(time)
	self.time = time
	if self.delay_timer then 
		self.delay_timer:remove() 
		self.delay_timer = nil
	end
	if time > 0 then 
		self.life_time = ac.clock() / 1000 + time - self.add_time
		self.delay_timer = self.owner:wait(time * 1000, function()
			if self.removed then 
				return 
			end 
			--补上一跳(计时器误差)
			if self.pulse then
				local target_pulse_count = self.life_time / self.pulse
				if target_pulse_count - 1 == self.pulse_count and self:is_enable() then
					self.pulse_count = self.pulse_count + 1
					self:notify('on_pulse', self.pulse_count)
				end
			end
			if not self.is_finish then 
				self.is_finish = true
				self:notify('on_finish')
			end
			self:remove()
		end)
		self.delay_time = time
	end 
end

--获得已经添加的时间
function mt:get_elapsed()
	return (ac.clock() / 1000) - self.add_time 
end 

--获得剩余时间
function mt:get_remaining()
	return self.life_time and math.max(0, self.life_time + self.add_time - ac.clock() / 1000) or -1
end

--设置为永久buff
function mt:permanent()
	self:set_remaining(-1)
end 

---获取buff图标
function mt:get_art()
	return self.art 
end 

---设置buff图标
function mt:set_art(art)
	self.art = art
end

---获取buff描述标题
function mt:get_tip()
	return format_obj(self,self.tip)
end 

---设置buff描述标题
function mt:set_tip(tip)
	self.tip = tip
	self:refresh()
end 

---获取buff描述字段
function mt:get_ubertip()
	return self.ubertip
end 

---设置buff描述字段
function mt:set_ubertip(ubertip)
	self.ubertip = ubertip 
	self:refresh()
end 

--自定义层数
mt.stack_count = 0

--获取自定义层数
function mt:get_stack()
	return self.stack_count
end

--设置自定义层数
function mt:set_stack(count)
	self.stack_count = count
end

--增加自定义层数
function mt:add_stack(count)
	self:set_stack(self:get_stack() + (count or 1))
end

-- 获取buff共存数量
function mt:get_cover_count()
	if self.cover_type == 1 then 
		local unit = self.owner 
		local name = self.name
		if unit.buff_list and unit.buff_list[name] then 
			return math.min(self.cover_max, #unit.buff_list[name])
		end 
	end 
	return 1 
end 

-- 获取buff进度
mt.progress_type = 1
function mt:get_progress()
	if self.time < 0 then 
		return 1.0
	end
	if self:is_aura() then 
		return 1.0
	end 
	local remain = self:get_remaining()
	if remain < 0 then 
		return 1.0
	end 
	if not self.delay_time or self.delay_time < 0 then 
		return 1.0
	end 
    local progress = 1 - (self:get_remaining() / self.delay_time)
    if self.progress_type==-1 then
        progress = 1 - progress
    end
	return progress
end

-- 刷新描述
function mt:refresh()
	if self.tip then 
		if self:is_debuff() then 
			self.tip_ui = "|cffff0000" .. self.tip .. "|r"
		else 
			self.tip_ui = "|cff00ff00" .. self.tip .. "|r"
		end 
	end 
	if self.ubertip then 
		self.ubertip_ui = string.formatter(self.ubertip, self)
	end 
end 

local ui = {

	art = function(self)
		return self.art 
	end,
	
    tip = function(self)
        return self:get_tip()
    end,
    ubertip = function(self)
        return self.ubertip_ui 
    end,
	-- 进度百分比
	progress = function(self)
		return self:get_progress()
	end,
	-- 层数
	count = function(self)
		if self.cover_type == 0 then 
			if self.stack_count > 0 then 
				return self.stack_count
			end
		elseif self.cover_type == 1 then 
			return self:get_cover_count()
		end
	end,
}

--获取UI信息
function mt:get_tooltips(name)
    if ui[name] then 
        return ui[name](self)
    end
end 

--加载模板数据
local function load_temp(data,temp)
    local temp_data = ac.buff[temp] or {}
    for k, v in pairs(temp_data) do
        if data[k] == nil then
            data[k] = v
        else
            data['temp_'..k] = v
        end
    end

    --追溯更早的模板
    local temp = temp_data.temp
    if temp then
        load_temp(data,temp)
    end
end


-- 添加Buff
function ac.unit.__index:add_buff(name)
	return function(data)
		local bff = data or { }
		local mt = ac.buff[name]
		bff.name = name
		bff.owner = self
		if not bff.source then 
			bff.source = self 
		end 
		local index = handle_manager:allocate()
		bff.syncid = index
		bff.gchash = index
        if bff.temp then
            load_temp(bff,bff.temp)
        end
		dbg.gchash(bff, index)
		setmetatable(bff,mt and mt.prototype or buff)
		return bff:add()
	end
end 

-- 获取buff表格
function ac.unit.__index:get_cover_buffs(name)
	if not self.buff_list then return end
	if not self.buff_list[name] then return end
	local info = {}
	for i = 1, #self.buff_list[name] do 
		info[i] = self.buff_list[name][i] 
	end 
	return info 
end 

-- 遍历buff
function ac.unit.__index:search_buffs(name)
	if not self.buffs then
		return function () end
	end
	local buffs = {}
	for bff in pairs(self.buffs) do
		buffs[bff] = true
	end
	if not name then
		return pairs(buffs)
	end
	local next, s, var = pairs(buffs)
	return function (s, var)
		while true do
			local r = next(s, var)
			if not r then return nil end
			if r.name == name then return r end
			var = r
		end
	end, s, var
end


--- 删除单位身上的bf
function ac.unit.__index:remove_buffs(is_death)
    for bff in self:search_buffs() do 
        if is_death==nil or not bff:is_keep() then
            bff:remove()
        end
    end
end 

--找buff
function ac.unit.__index:find_buff(name)
	if not self.buffs then
		return
	end
	local add_time 
	local result 
	for bff in self:search_buffs(name) do 
		if bff.name == name then 
			if not add_time then
				add_time = bff.add_time
				result = bff
			else 
				if bff.add_time < add_time then 
					add_time = bff.add_time
					result = bff
				end 
			end
		end
	end
	return result
end

-- 添加绑定特效
function mt:add_effect(model,socket)
	if not self.model_list then 
		self.model_list = {}
	end 
	local effect = self.owner:add_attach(model, socket)
	table.insert(self.model_list, effect)
	return effect
end 

-- 删除所有绑定的特效
function mt:remove_effects()
	if self.model_list then 
		for i=#self.model_list,1,-1 do 
			self.model_list[i]:remove()
		end  
	end 
	self.model_list = nil
end 

function mt:add_icon()
	local unit = self.owner 
    if not unit.play_buffs then 
        unit.play_buffs = {}
    end
    if not self.played_flag and (self.art and self.tip and self.ubertip) then 
        self.played_flag = true
        if self.cover_type == 0 then 
            table.insert(unit.play_buffs, self)
        elseif self.cover_type == 1 then 
            for i = 1, #unit.play_buffs+1, 1 do 
                local pbuff = unit.play_buffs[i]
                if pbuff and pbuff.name == self.name then 
                    local before = pbuff
                    while before.node_after do 
                        before = before.node_after
                    end 
                    -- 链接 两个buff
                    before.node_after = self 
                    self.node_before = before
                    break
                end 
                if i == (#unit.play_buffs+1) then 
					self.delay_time = self:get_remaining()
					self.node_header = true
                    unit.play_buffs[i] = self
                end
            end 
        end
    end
end 

function mt:remove_icon()
	local unit = self.owner 
    if unit.play_buffs and self.played_flag then 
        self.played_flag = false 
        if self.cover_type == 0 then 
            for i = #unit.play_buffs, 1, -1 do 
                local pbuff = unit.play_buffs[i]
                if pbuff == self then 
                    table.remove(unit.play_buffs, i)
                    break
                end 
            end 
        elseif self.cover_type == 1 then 
            local before = self.node_before
            local after = self.node_after
			self.node_before = nil 
			self.node_after = nil 
            if before then 
                before.node_after = after
                if after then  
                    after.node_before = before
                end
            elseif self.node_header then 
				self.node_header = false 
                for i = #unit.play_buffs, 1, -1 do 
                    local pbuff = unit.play_buffs[i]
                    if pbuff and pbuff == self then 
                        if not after then 
                            table.remove(unit.play_buffs, i)
                        else
							after.delay_time = after:get_remaining()
							after.node_header = true
                            after.node_before = nil 
                            unit.play_buffs[i] = after
                        end 
                        break
                    end 
                end 
            end
        end 
    end 
end 

-- 获取公共数据表 用于共存buff
function mt:get_public_data(name)
	if self.cover_type == 1 then 
		local data = self.owner:get_data(("buff-public-{%s}"):format(self.name))
		if data then 
			return data[name]
		end 
	end 
end 

-- 设置公共数据表 一般用于共存buff
function mt:set_public_data(name, value)
	if self.cover_type == 1 then 
		local data = self.owner:get_data(("buff-public-{%s}"):format(self.name))
		if data then 
			data[name] = value
		end 
	end 
end 

function buff.init() 
	require 'types.buff.沉默'
	require 'types.buff.浮空'
	require 'types.buff.光环'
	require 'types.buff.护盾'
	require 'types.buff.击飞'
	require 'types.buff.隐身'
	require 'types.buff.晕眩'
	require 'types.buff.弹道'
	require 'types.buff.位移'
	require 'types.buff.召唤'
end 

-- 构造
ac.buff = {}
setmetatable(ac.buff,{
	__index = function(self,name)
		local mt = {}
		mt.name = name
		mt.prototype = {
			__index = mt,
			__tostring = buff.__tostring,
		}
		local data = ac.lni('buff', name)
		if data then 
            setmetatable(data, buff)
            data.__index = data
            data.__tostring = buff.__tostring	
			data.__call = buff.__call 
			setmetatable(mt, data)
		else 
			setmetatable(mt, buff)
		end 
		self[name] = mt 
		return mt
    end,
	__call = function(self, name)
        return ac.buff[name]
	end,
})

return buff 